若我們的Router設定較為複雜時,可將Router
配置為一個Router Module。
首先,設定一個路由模組app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CrisisListComponent } from './crisis-list.component';
import { HeroListComponent } from './hero-list.component';
import { PageNotFoundComponent } from './not-found.component';
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component: HeroListComponent },
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
接著,更新app.module.ts
文件
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { CrisisListComponent } from './crisis-list.component';
import { HeroListComponent } from './hero-list.component';
import { PageNotFoundComponent } from './not-found.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule
],
declarations: [
AppComponent,
HeroListComponent,
CrisisListComponent,
PageNotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
我們在做網站時,會希望每個模組專注在自己的功能上,Router也不例外。因此像英雄列表的元件,如果希望點選列表內容,可以進入該英雄詳細資料的頁面如下:
首先,創建一個pre-routing檔案heroes.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';
@NgModule({
imports: [
CommonModule,
FormsModule,
],
declarations: [
HeroListComponent,
HeroDetailComponent
],
providers: [ HeroService ]
})
export class HeroesModule {}
新增一個heroes-routing.module.ts
,內容如下:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
const heroesRoutes: Routes = [
{ path: 'heroes', component: HeroListComponent },
{ path: 'hero/:id', component: HeroDetailComponent }
];
@NgModule({
imports: [
RouterModule.forChild(heroesRoutes)
],
exports: [
RouterModule
]
})
export class HeroRoutingModule { }
將Router Module的檔案放在與其伴隨的模組在相同的文件夾中。這兩個
heroes-routing.module.ts
和heroes.module.ts
在同一個src/app/heroes
文件夾中。
考慮給每個功能模組自己的路由配置文件。當功能路線很簡單時,看起來可能會過早。但是,路線趨於變得更加複雜,模式的一致性會隨著時間的推移而得到回報。
接著在heroes.module.ts
裡導入HeroRoutingModule
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';
import { HeroRoutingModule } from './heroes-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
HeroRoutingModule
],
declarations: [
HeroListComponent,
HeroDetailComponent
],
providers: [ HeroService ]
})
export class HeroesModule {}
path: 'heroes'
目前定義在兩個地方:HeroesRoutingModule和AppRoutingModule。
由功能模塊提供的route由Router合併到其導入的模塊的route中。這使您可以繼續定義功能模塊路由,而無需修改主路由配置。
如果不想設定兩次相同的資料,可以把舊的Router設定移除
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CrisisListComponent } from './crisis-list.component';
// import { HeroListComponent } from './hero-list.component';
// <-- delete this line
import { PageNotFoundComponent } from './not-found.component';
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
// { path: 'heroes', component: HeroListComponent },
// <-- delete this line
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
在應用程式裡面使用AppRoutingModule,打開src/app/app.module.ts
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HeroesModule } from './heroes/heroes.module';
import { CrisisListComponent } from './crisis-list.component';
import { PageNotFoundComponent } from './not-found.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HeroesModule,
AppRoutingModule
],
declarations: [
AppComponent,
CrisisListComponent,
PageNotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
這邊要注意的是模塊導入順序很重要
當所有的路由都在AppRoutingModule時,我們將通用path: '**'
符號的設定放在路由/heroes
的後面,這樣路由器才有機會匹配一個URL到/heroes路由,而不會先被AppRoutingModule
的這個設定匹配走。
{ path: '**', component: PageNotFoundComponent }
如果Routing在兩個不同的模組內,也是要先載入HeroesModule再載入AppRoutingModule,這樣才會使用HeroesModule裡的路由設定而不會因為在AppRoutingModule找到匹配的通用符號而顯示PageNotFoundComponent。
如果將import的順序倒過來,則會顯示PageNotFoundComponent的頁面。
使用this.router.navigate
來在程式內操作路由工作
gotoHeroes() {
this.router.navigate(['/heroes']);
}
若需要傳一個參數,則可以用
gotoHeroes() {
this.router.navigate(['/hero', hero.id]);
}
要傳送兩個參數則如下
gotoHeroes(hero: Hero) {
this.router.navigate(['/hero', { id: hero.id, foo: 'foo' }]);
}
點下後出現的連結如下
localhost:3000/heroes;id=15;foo=foo
這個叫做Matrix URL
,在URL查詢字符串中,用分號隔開“;” 不同的參數,而不是用&
export class HeroListComponent implements OnInit {
heroes$: Observable<Hero[]>;
private selectedId: number;
constructor(
private service: HeroService,
private route: ActivatedRoute
) {}
ngOnInit() {
this.heroes$ = this.route.paramMap
.switchMap((params: ParamMap) => {
// (+) before `params.get()` turns the string into a number
this.selectedId = +params.get('id');
return this.service.getHeroes();
});
}
}
首先要載入BrowserAnimationsModule
這個動態效果模組。
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
imports: [
BrowserAnimationsModule
animations.ts
在根src/app/
文件夾中創建一個文件。內容如下所示:
import { animate, AnimationEntryMetadata, state, style, transition, trigger } from '@angular/core';
// Component transition animations
export const slideInDownAnimation: AnimationEntryMetadata =
trigger('routeAnimation', [
state('*',
style({
opacity: 1,
transform: 'translateX(0)'
})
),
transition(':enter', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('0.2s ease-in')
]),
transition(':leave', [
animate('0.5s ease-out', style({
opacity: 0,
transform: 'translateY(100%)'
}))
])
]);
然後在src/app/heroes/hero-detail.component.ts
增加使用這個動態效果的綁定
@HostBinding('@routeAnimation') routeAnimation = true;
@HostBinding('style.display') display = 'block';
@HostBinding('style.position') position = 'absolute';
子路由元件與直接使用參數去定義路由,Router預設的狀況下,若瀏覽器重新導航到相同的元件時,會重新使用該元件既有的實體,而不會重新創建。因此在物件被重用的狀況下,該元件的ngOnInit
只會被呼叫一次,即使是要顯示不同的內容資料。
但是被創建的元件實體會在離開頁面時被銷毀並取消註冊,因此在前面範例的heroes-routing.module.ts
檔案中裡,所使用的導航設定方式,由於在瀏覽HeroDetailComponent
之後,一定要先回到HeroListComponent
,才能進入下一個Detail頁面。
const heroesRoutes: Routes = [
{ path: 'heroes', component: HeroListComponent },
{ path: 'hero/:id', component: HeroDetailComponent }
];
這會造成因為在回到HeroListComponent
時已把HeroDetailComponent
刪除掉了,再選擇另一個英雄要查看細節時,又會再創立一個新的HeroDetailComponent
。因此每次選擇不同的英雄時,組件都會重新創建。
如果想要保留頁面的狀態,就可以改使用子路由的方式來定義
下面是使用子路由的範例:
const crisisCenterRoutes: Routes = [
{
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{
path: '',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
}
]
}
];
因為CrisisDetailComponent
與CrisisCenterHomeComponent
都是CrisisListComponent
的子組件,因此在不同的子組件內的狀態得以被保存,直到頁面切換離開CrisisCenterComponent
時才會將元件實體刪除,這可以讓我們在瀏覽不同CrisisDetail時,得以使用到Router預設的重用設定。
./
是在目前的位置。../
在上一層的位置。
下面為使用範例
// Relative navigation back to the crises
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });
很多時候某些頁面或許需要登入才可以檢視,有些要登入後具備某些身份才可以開啟該網址,這時候就可以用到Router的CanActivate
功能。
下面是src/app/auth-guard.service.ts
的內容:
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate() {
console.log('AuthGuard#canActivate called');
return true;
}
}
下面是src/app/admin/admin-routing.module.ts
的內容
import { AuthGuard } from '../auth-guard.service';
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{
path: '',
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
],
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
本篇所有範例可見:live demo / download example.
取得Router的參數
ngOnInit() {
this.heroes$ = this.route.paramMap
.switchMap((params: ParamMap) => {
這一段在paramMap部分是不是少了.pipe(switchMap ...)
還是NG 5的時代不需要XD?